package git.soulbgm;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.*;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 程序入口
 *
 * @author SoulBGM
 * @date 2021/07/15
 */
public class Application {

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class User {
        private Integer id;
        private String name;
        private String sex;
        private Integer age;
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class Student {
        private Integer id;
        private String name;
        private Integer grade;
        private Integer age;
    }

    /**
     * 构造流(Stream)的几种常见方法
     */
    public void createStream() {
        // 1.使用Stream创建
        Stream<String> stream1 = Stream.of("a", "b", "c");
        // 2.使用Arrays创建
        String[] strArrays = {"a", "b", "c"};
        Stream<String> stream2 = Stream.of(strArrays);
        Stream<String> stream3 = Arrays.stream(strArrays);
        // 3.使用Collections创建
        List<String> list = Arrays.asList("a", "b", "c");
        Stream<String> stream4 = list.stream();
    }

    /**
     * 流转换其他数据结构
     */
    public void streamToDataStructure() {
        // 源数据
        List<String> sourceData = Arrays.asList("a", "b", "c");
        // 1.流转Array
        Stream<String> stream = sourceData.stream();
        String[] strArrays = stream.toArray(String[]::new);
        // 2.流转Collection
        stream = sourceData.stream();
        List<String> list1 = stream.collect(Collectors.toList());

        stream = sourceData.stream();
        List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));

        stream = sourceData.stream();
        Set<String> set1 = stream.collect(Collectors.toSet());

        stream = sourceData.stream();
        Set<String> set2 = stream.collect(Collectors.toCollection(HashSet::new));

        stream = sourceData.stream();
        Stack<String> stack = stream.collect(Collectors.toCollection(Stack::new));
        // 3.流转String
        stream = sourceData.stream();
        String str1 = stream.collect(Collectors.joining());
        stream = sourceData.stream();
        String str2 = stream.collect(Collectors.joining(","));
    }

    /**
     * 流的map操作
     */
    public void streamMapOperate() {
        // 源数据 将字母都转为大写字母
        List<String> sourceData1 = Arrays.asList("a", "b", "c");
        List<String> list1 = sourceData1.stream()
                .map(String::toUpperCase)
                .collect(Collectors.toList());
        list1.forEach(System.out::println);

        // 源数据 将字母都转为大写字母
        List<User> sourceData2 = Arrays.asList(
                User.builder().id(1).name("张三").sex("男").age(18).build(),
                User.builder().id(2).name("张四").sex("女").age(29).build(),
                User.builder().id(3).name("张五").sex("女").age(35).build(),
                User.builder().id(4).name("张六").sex("男").age(28).build(),
                User.builder().id(5).name("李七").sex("男").age(32).build(),
                User.builder().id(6).name("王李子").sex("男").age(31).build()
        );
        List<Integer> list2 = sourceData2.stream()
                .map(User::getId)
                .collect(Collectors.toList());
        list2.forEach(System.out::println);
    }

    /**
     * 流的flatMap操作
     */
    public void streamFlatMapOperate() {
        // 源数据 将多个List合并为一个List，并将内容进行转换
        List<List<String>> sourceData1 = Arrays.asList(
                Arrays.asList("a", "b", "c"),
                Arrays.asList("d", "e", "f"),
                Arrays.asList("g", "h", "l")
        );

        List<String> list1 = sourceData1.stream()
                .flatMap(List::stream)
                .map(String::toUpperCase)
                .collect(Collectors.toList());
        list1.forEach(System.out::println);

        // 源数据 将多个String数组合并去重，最后已逗号隔开
        List<String[]> sourceData2 = Arrays.asList(
                new String[] {"a", "b", "c"},
                new String[] {"d", "e", "f"},
                new String[] {"g", "c", "f"}
        );

        String result = sourceData2.stream()
                .flatMap(Arrays::stream)
                .distinct()
                .collect(Collectors.joining(","));
        System.out.println(result);
    }

    /**
     * 流的list转map操作
     */
    public void streamListToMapOperate() {
        // 源数据 将字母都转为大写字母
        List<String> sourceData1 = Arrays.asList("a", "b", "c");
        List<String> list1 = sourceData1.stream()
                .map(String::toUpperCase)
                .collect(Collectors.toList());
        list1.forEach(System.out::println);

        // 源数据 将字母都转为大写字母
        List<User> sourceData2 = Arrays.asList(
                User.builder().id(1).name("张三").sex("男").age(18).build(),
                User.builder().id(2).name("张四").sex("女").age(29).build(),
                User.builder().id(3).name("张五").sex("女").age(35).build(),
                User.builder().id(4).name("张六").sex("男").age(28).build(),
                User.builder().id(5).name("李七").sex("男").age(32).build(),
                User.builder().id(6).name("王李子").sex("男").age(31).build()
        );
        // 通过ID得到用户信息
        Map<Integer, User> map = sourceData2.stream()
                .collect(Collectors.toMap(User::getId, Function.identity()));
        System.out.println(map);

        // 通过姓名得到年龄
        Map<String, Integer> map1 = sourceData2.stream()
                .collect(Collectors.toMap(User::getName, User::getAge));
        System.out.println(map1);

        // 通过ID和姓名得到年龄
        Map<String, Integer> map2 = sourceData2.stream()
                .collect(Collectors.toMap(o -> o.getId() + o.getName(), User::getAge));
        System.out.println(map2);

        // 当有相同key冲突时 使用(k1, k2) -> k2, 只保留后面那个元素
        Map<String, Integer> map3 = sourceData2.stream()
                .collect(Collectors.toMap(User::getSex, User::getAge, (k1, k2) -> k2));
        System.out.println(map3);

        // 通过比较值进行选择保留的内容
        Map<String, Integer> map4 = sourceData2.stream()
                .collect(Collectors.toMap(User::getSex, User::getAge, BinaryOperator.maxBy(Comparator.comparingInt(o -> o))));
        System.out.println(map4);

        // 自定义处理冲突
        Map<String, Integer> map5 = sourceData2.stream()
                .collect(Collectors.toMap(User::getSex, User::getAge, (k1, k2) -> {
                    if (k2 < k1) {
                        return k2;
                    }
                    return k1;
                }));
        System.out.println(map5);
    }

    /**
     * 流的filter操作
     */
    public void streamFilterOperate() {
        // 源数据 找出包含张的字符串
        List<String> sourceData1 = Arrays.asList("张三", "张狂徒", "李四");
        List<String> list1 = sourceData1.stream()
                .filter(s -> s.contains("张"))
                .collect(Collectors.toList());
        list1.forEach(System.out::println);

        // 源数据 找出60以下的数
        List<Integer> sourceData2 = Arrays.asList(100, 82, 91, 60, 72, 10, 20, 33);
        List<Integer> list2 = sourceData2.stream()
                .filter(num -> num < 60)
                .collect(Collectors.toList());
        list2.forEach(System.out::println);

        // 源数据 找出性别为男并且年龄在30以上姓李的用户
        List<User> sourceData3 = Arrays.asList(
                User.builder().id(1).name("张三").sex("男").age(18).build(),
                User.builder().id(2).name("张四").sex("女").age(29).build(),
                User.builder().id(3).name("张五").sex("女").age(35).build(),
                User.builder().id(4).name("张六").sex("男").age(28).build(),
                User.builder().id(5).name("李七").sex("男").age(32).build(),
                User.builder().id(6).name("王李子").sex("男").age(31).build()
        );
        List<User> list3 = sourceData3.stream()
                .filter(u -> "男".equals(u.getSex()))
                .filter(u -> u.getAge() > 30)
                .filter(u -> u.getName().startsWith("李"))
                .collect(Collectors.toList());
        list3.forEach(System.out::println);
    }

    /**
     * 流的max操作
     */
    public void streamMaxOperate() {
        // 源数据 找出最大的数
        List<Integer> sourceData1 = Arrays.asList(100, 82, 91, 91, 72, 10, 20, 33);
        // (x, y) -> (x < y) ? -1 : ((x == y) ? 0 : 1)
        // (x, y) -> Integer.compare(x, y)
        Integer maxNum = sourceData1.stream().max(Integer::compare).get();
        System.out.println(maxNum);

        // 源数据 找出最大年龄的用户
        List<User> sourceData2 = Arrays.asList(
                User.builder().id(1).name("张三").sex("男").age(18).build(),
                User.builder().id(2).name("张四").sex("女").age(29).build(),
                User.builder().id(3).name("张五").sex("女").age(35).build(),
                User.builder().id(4).name("张六").sex("男").age(28).build(),
                User.builder().id(5).name("李七").sex("男").age(32).build(),
                User.builder().id(6).name("王李子").sex("男").age(31).build()
        );
        User user = sourceData2.stream().max((x, y) -> Integer.compare(x.getAge(), y.getAge())).get();
        System.out.println(user);
    }

    /**
     * 流的min操作
     */
    public void streamMinOperate() {
        // 源数据 找出最大的数
        List<Integer> sourceData1 = Arrays.asList(100, 82, 91, 91, 72, 10, 20, 33);
        // (x, y) -> (x < y) ? -1 : ((x == y) ? 0 : 1)
        // (x, y) -> Integer.compare(x, y)
        Integer maxNum = sourceData1.stream().min(Integer::compare).get();
        System.out.println(maxNum);

        // 源数据 找出最小年龄的用户
        List<User> sourceData2 = Arrays.asList(
                User.builder().id(1).name("张三").sex("男").age(18).build(),
                User.builder().id(2).name("张四").sex("女").age(29).build(),
                User.builder().id(3).name("张五").sex("女").age(35).build(),
                User.builder().id(4).name("张六").sex("男").age(28).build(),
                User.builder().id(5).name("李七").sex("男").age(32).build(),
                User.builder().id(6).name("王李子").sex("男").age(31).build()
        );
        User user = sourceData2.stream().min((x, y) -> Integer.compare(x.getAge(), y.getAge())).get();
        System.out.println(user);
    }

    /**
     * 流的distinct操作
     */
    public void streamDistinctOperate() {
        // 源数据 去重
        List<Integer> sourceData1 = Arrays.asList(100, 82, 91, 91, 72, 10, 20, 33);
        List<Integer> list1 = sourceData1.stream().distinct().collect(Collectors.toList());
        System.out.println(list1);
    }

    /**
     * 流的limit操作
     */
    public void streamLimitOperate() {
        // 源数据 获取前几位数据
        List<Integer> sourceData1 = Arrays.asList(100, 82, 91, 91, 72, 10, 20, 33);
        List<Integer> list1 = sourceData1.stream().limit(3).collect(Collectors.toList());
        System.out.println(list1);
    }

    /**
     * 流的skip操作
     */
    public void streamSkipOperate() {
        // 源数据 舍弃前几位数据
        List<Integer> sourceData1 = Arrays.asList(100, 82, 91, 91, 72, 10, 20, 33);
        List<Integer> list1 = sourceData1.stream().skip(3).collect(Collectors.toList());
        System.out.println(list1);
    }

    /**
     * 流的排序操作
     */
    public void streamSortedOperate() {
        // 源数据 排序 从小到大
        List<Integer> sourceData1 = Arrays.asList(100, 82, 91, 91, 72, 10, 20, 33);
        List<Integer> list1 = sourceData1.stream().sorted().collect(Collectors.toList());
        System.out.println(list1);

        // 源数据 排序 从大到小
        List<Integer> sourceData2 = Arrays.asList(100, 82, 91, 91, 72, 10, 20, 33);
        List<Integer> list2 = sourceData2.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
        System.out.println(list2);

        // 对学生的年级从高到低排序 之后 对年龄从低到高排序
        List<Student> sourceData3 = new ArrayList<>(6);
        sourceData3.add(Student.builder().id(1).name("zs1").grade(1).age(18).build());
        sourceData3.add(Student.builder().id(2).name("zs2").grade(2).age(18).build());
        sourceData3.add(Student.builder().id(3).name("zs3").grade(3).age(18).build());
        sourceData3.add(Student.builder().id(4).name("zs4").grade(2).age(17).build());
        sourceData3.add(Student.builder().id(5).name("zs5").grade(2).age(19).build());
        sourceData3.add(Student.builder().id(6).name("zs6").grade(1).age(14).build());
        List<Student> list3 = sourceData3.stream().sorted(
                Comparator.comparingInt(Student::getGrade).reversed()
                        .thenComparingInt(Student::getAge)).collect(Collectors.toList());
        list3.forEach(d -> {
            System.out.printf("id:%s name:%s grade:%s age:%s\n", d.id, d.name, d.grade, d.age);
        });

        // 对学生的年龄从低到高排序 取前3个年龄最小的学生姓名
        sourceData3.stream().sorted(Comparator.comparingInt(Student::getAge)).limit(3).map(Student::getName).forEach(System.out::println);

        // 对学生的年龄从低到高排序 取年龄最大的3个的学生姓名
        sourceData3.stream().sorted(Comparator.comparingInt(Student::getAge).reversed()).limit(3).map(Student::getName).forEach(System.out::println);
    }

    /**
     * 流的分组的操作
     */
    public void streamGroupingByOperate() {
        // 源数据 进行性别分类
        List<User> sourceData1 = Arrays.asList(
                User.builder().id(1).name("张三").sex("男").age(18).build(),
                User.builder().id(2).name("张四").sex("女").age(29).build(),
                User.builder().id(3).name("张五").sex("女").age(35).build(),
                User.builder().id(4).name("张六").sex("男").age(28).build(),
                User.builder().id(5).name("李七").sex("男").age(32).build(),
                User.builder().id(6).name("王李子").sex("男").age(31).build()
        );
        Map<String, List<User>> map1 = sourceData1.stream().collect(Collectors.groupingBy(User::getSex));
        System.out.println(map1);

        // 按性别 得到对应用户的ID
        Map<String, List<Integer>> map2 = sourceData1.stream().collect(Collectors.groupingBy(User::getSex, Collectors.mapping(User::getId, Collectors.toList())));
        System.out.println(map2);
    }

    /**
     * 流分组的计数操作
     */
    public void streamGroupingByCountOperate() {
        // 源数据 进行性别分类并计数
        List<User> sourceData1 = Arrays.asList(
                User.builder().id(1).name("张三").sex("男").age(18).build(),
                User.builder().id(2).name("张四").sex("女").age(29).build(),
                User.builder().id(3).name("张五").sex("女").age(35).build(),
                User.builder().id(4).name("张六").sex("男").age(28).build(),
                User.builder().id(5).name("李七").sex("男").age(32).build(),
                User.builder().id(6).name("王李子").sex("男").age(31).build()
        );
        Map<String, Long> map1 = sourceData1.stream().collect(Collectors.groupingBy(User::getSex, Collectors.counting()));
        System.out.println(map1);
        // 按性别统计 男女生年龄的总和
        Map<String, Integer> map2 = sourceData1.stream().collect(Collectors.groupingBy(User::getSex, Collectors.summingInt(User::getAge)));
        System.out.println(map2);

        // 源数据 进行相同字符串计数
        List<String> sourceData2 = Arrays.asList(
                "2991,1991",
                "8821,102",
                "2991,1991",
                "2992,1993",
                "2992,19911",
                "2992,1923",
                "2992,1923"
        );
        ConcurrentMap<String, Long> map3 = sourceData2.stream().collect(Collectors.groupingByConcurrent(Function.identity(), Collectors.counting()));
        System.out.println(map3);
    }

    /**
     * 流分组后再分组操作
     */
    public void streamGroupingAfterThenAgainGroupingOperate() {
        // 源数据 先进行性别分类再进行年龄区间分类
        List<User> sourceData1 = Arrays.asList(
                User.builder().id(1).name("张三").sex("男").age(18).build(),
                User.builder().id(2).name("张四").sex("女").age(29).build(),
                User.builder().id(3).name("张五").sex("女").age(35).build(),
                User.builder().id(4).name("张六").sex("男").age(28).build(),
                User.builder().id(5).name("李七").sex("男").age(32).build(),
                User.builder().id(6).name("王李子").sex("男").age(31).build()
        );

        Map<String, Map<String, List<User>>> map = sourceData1.stream().collect(Collectors.groupingBy(User::getSex, Collectors.groupingBy(o -> getAgeClassify(o.getAge()))));
        map.forEach((k, v) -> {
            System.out.println("性别: " + k);
            System.out.println("列表: ");
            v.forEach((c, l) -> System.out.println("属于: " + c + ", 有 " + l.stream().map(User::getName).collect(Collectors.joining(","))));
        });

        // 先进行性别分类再进行年龄区间分类最后统计人数
        Map<String, Map<String, Long>> map2 = sourceData1.stream().collect(Collectors.groupingBy(User::getSex, Collectors.groupingBy(o -> getAgeClassify(o.getAge()), Collectors.counting())));
        map2.forEach((k, v) -> {
            System.out.println("性别: " + k);
            System.out.println("列表: ");
            v.forEach((c, count) -> System.out.printf("属于: %s, 有%s人\n", c, count));
        });
    }

    /**
     * 获取年龄分类
     *
     * @param age 年龄
     * @return {@link String}
     */
    public String getAgeClassify(Integer age) {
        if (age >= 0 && age <=16) {
            return "儿童";
        } else if (age >= 17 && age <= 30) {
            return "青年";
        } else if (age >= 31 && age <= 55) {
            return "中年";
        } else {
            return "老年";
        }
    }

    /**
     * 流的求和操作
     */
    public void streamSummingOperate() {
        // 源数据 进行求和
        List<User> sourceData1 = Arrays.asList(
                User.builder().id(1).name("张三").sex("男").age(18).build(),
                User.builder().id(2).name("张四").sex("女").age(29).build(),
                User.builder().id(3).name("张五").sex("女").age(35).build(),
                User.builder().id(4).name("张六").sex("男").age(28).build(),
                User.builder().id(5).name("李七").sex("男").age(32).build(),
                User.builder().id(6).name("王李子").sex("男").age(31).build()
        );
        Integer sumAge = sourceData1.stream().mapToInt(User::getAge).sum();
        System.out.println(sumAge);
    }

    /**
     * 流的汇总统计操作
     */
    public void streamSummaryStatisticsOperate() {
        // 源数据 进行汇总统计
        List<User> sourceData1 = Arrays.asList(
                User.builder().id(1).name("张三").sex("男").age(18).build(),
                User.builder().id(2).name("张四").sex("女").age(29).build(),
                User.builder().id(3).name("张五").sex("女").age(35).build(),
                User.builder().id(4).name("张六").sex("男").age(28).build(),
                User.builder().id(5).name("李七").sex("男").age(32).build(),
                User.builder().id(6).name("王李子").sex("男").age(31).build()
        );
        IntSummaryStatistics statistics = sourceData1.stream().mapToInt(User::getAge).summaryStatistics();
        System.out.println("总共人数:" + statistics.getCount());
        System.out.println("平均年龄:" + statistics.getAverage());
        System.out.println("最大年龄:" + statistics.getMax());
        System.out.println("最小年龄:" + statistics.getMin());
        System.out.println("年龄之和:" + statistics.getSum());
    }

    public static void main(String[] args) {
        Application app = new Application();
        app.streamFlatMapOperate();
    }

}
